using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using ClientGUI.Modules.Drawing;
using OWED.Document;
using System.Windows.Forms;
using System.Drawing;

//TODO:  Fix changeAtom.Apply so that it does the sync work with the XmlDocument


namespace ClientGUI
{
    /// <summary>
    /// This is the base class for all drawing/interaction modules for
    /// our docbook display.
    /// </summary>
    public abstract class Module : IDisposable
    {
        #region member variables
        /// <summary>
        /// children of this module (modules contained inside this module)
        /// </summary>
        protected ChildCollection my_kids;

        /// <summary>
        /// Changes between a select and unselect
        /// </summary>
        protected List<ChangeAtom> my_changes_since_select;

        /// <summary>
        /// parent of this module
        /// </summary>
        protected Module my_parent;

        /// <summary>
        /// true iff this module is currently selected
        /// </summary>
        protected Boolean selected;

        /// <summary>
        /// True iff this module currently has the mouse over it as the
        /// most specific element.  It may be true while selected is true,
        /// in which case selected should take precedence.
        /// </summary>
        protected Boolean hoovered;

        /// <summary>
        /// Width of this module.
        /// </summary>
        protected int my_width;

        /// <summary>
        /// Height of this module
        /// </summary>
        protected int my_height;

        /// <summary>
        /// name of this module (eg para, chapter, ect...)
        /// </summary>
        protected String my_name;

        /// <summary>
        /// XmlNode that backs this module.
        /// </summary>
        protected XmlNode myNode;

        /// <summary>
        /// Display document that is drawing this module
        /// </summary>
        protected DisplayDocument my_host;

        /// <summary>
        /// Keeps all the attributes of each module type, eg possible children
        /// tags
        /// </summary>
        protected TagConfig my_tagconfig;

        /// <summary>
        /// Attributes of the tag associated with this module.
        /// </summary>
        protected Dictionary<String, String> my_attributes;

        /// <summary>
        /// Used if a module needs a small pading on the left before children
        /// are placed
        /// </summary>
        protected int my_left_buffer;

        /// <summary>
        /// Used for padding the inside of contianer on the right
        /// </summary>
        protected int my_right_buffer;

        /// <summary>
        /// Used if a module needs a small pading on the top before children are
        /// placed.
        /// </summary>
        protected int my_top_buffer;

        /// <summary>
        /// Used for padding the bottom if the inside of a container.
        /// </summary>
        protected int my_bottom_buffer;

        /// <summary>
        /// Used for padding the OUTSIDE of a container on the top
        /// </summary>
        protected int my_top_space;

        /// <summary>
        /// Used for padding the OUTSIDE of a container on the bottom
        /// </summary>
        protected int my_bottom_space;

        /// <summary>
        /// Used to make sure controls never go invisible
        /// </summary>
        protected int my_min_height;

        /// <summary>
        /// Used to make sure controls never go invisible
        /// </summary>
        protected int my_min_width;


        #endregion

        /// <summary>
        /// This Construct a new module.
        /// </summary>
        /// <param name="doc">The section of the document which
        /// this represents.</param>
        /// <param name="parent">The parent of this module</param>
        /// <param name="host">The host of this which does the
        /// actual display management.</param>
        /// <param name="tagconfig">The configuration for this tag (eg children and attributes)</param>
        public Module(XmlNode doc, Module parent, DisplayDocument host,
                    TagConfig tagconfig)
        {
            my_kids = new ChildCollection(Insert, Remove);
            my_parent = parent;
            //width and height are calculated in subclasses and on module addition
            my_width = 0;
            my_height = 0;
            my_host = host;
            my_tagconfig = tagconfig;

            my_attributes = new Dictionary<string, string>();
            if (doc != null)
            {
                if (doc.Attributes != null)
                {
                    foreach (XmlAttribute attr in doc.Attributes)
                    {
                        my_attributes.Add(
                            attr.Name, attr.Value);
                    }

                    my_name = doc.Name;
                }
            }
            myNode = doc;
            my_left_buffer = 15;
            my_top_buffer = 5;
            my_bottom_buffer = 5;
            my_right_buffer = 5;
            my_top_space = 1;
            my_bottom_space = 1;

            my_min_height = 18;
            my_min_width = 5;

        }

        /// <summary>
        /// Gets an xml attribute by name for this tag.
        /// </summary>
        /// <param name="name">the name</param>
        /// <returns>the value, or null</returns>
        public String GetAttribute(String name)
        {
            String value = null;
            my_attributes.TryGetValue(name, out value);
            return value;
        }

        /// <summary>
        /// Sets the attribute name to be value, updates the in-memory xml file.
        /// Creates a ChangeAtom and adds it to the global queue of change
        /// atoms.
        /// </summary>
        /// <param name="name">name of attribute to modify</param>
        /// <param name="value">attribute to give it</param>
        public void SetAttribute(String name, String value)
        {
            my_attributes[name] = value;
            XmlAttribute attr = (XmlAttribute)Node.Attributes.GetNamedItem(name);
            if (attr != null)
            {
                attr.Value = value;
            }
            else
            {
                attr = Node.OwnerDocument.CreateAttribute(name);
                attr.Value = value;
                Node.Attributes.Append(attr);
            }
            

        }


        /// <summary>
        /// Tag Configuration of this module (for properties pane)
        /// </summary>
        public TagConfig TagConfig { get { return my_tagconfig; } }

        /// <summary>
        /// Parent of this module
        /// </summary>
        public Module Parent
        {
            get { return my_parent; }
            set { my_parent = value; }
        }

        /// <summary>
        /// Host of this module, that is the control that contains this module
        /// (also the creator of this module in most situations).
        /// </summary>
        public DisplayDocument Host { get { return my_host; } }


        /// <summary>
        /// Return a mutable copy of the Children that this module contains, 
        /// children are modules that are contained "inside" in a xml file
        /// and the display.
        /// </summary>
        public ChildCollection Children
        {
            get
            {
                return my_kids;
            }
        }

        /// <summary>
        /// Name of this module, eg "para" "book" etc
        /// </summary>
        public String Name
        {
            get
            {
                return my_name;
            }
        }

        /// <summary>
        /// Width of this module
        /// </summary>
        public int Width { get { return my_width; } }

        /// <summary>
        /// Height of this module
        /// </summary>
        public virtual int Height
        {
            get
            {
                return my_height;
            }
            set
            {
                if (Parent != null)
                    Parent.Height += value - my_height;
                my_height = value;
            }
        }

        /// <summary>
        /// Left buffer of this module.  Used for small pad spacing
        /// during layout on the INSIDE of the module.
        /// </summary>
        public int TopBuffer { get { return my_top_buffer; } }

        /// <summary>
        /// Bottom buffer of this module, not really used except to calculate space
        /// requirements of containers.  It is the amount of padding to put on the INSIDE
        /// of the Module before the first child.
        /// </summary>
        public int BottomBuffer { get { return my_bottom_buffer; } }

        /// <summary>
        /// Top buffer of this module.  Used for small pad spacing
        /// during layout on the INSIDE of the module.
        /// </summary>
        public int LeftBuffer { get { return my_left_buffer; } }

        /// <summary>
        /// Right buffer of this container on the INSIDE
        /// </summary>
        public int RightBuffer { get { return my_right_buffer; } }

        /// <summary>
        /// Top space of this contianer, on the OUTSIDE (amout of space to
        /// leave after the above control)
        /// </summary>
        public int TopSpace { get { return my_top_space; } }

        /// <summary>
        /// Bottom space of this contianer on the OUTSIDE (amount of space
        /// to leave before the next control).
        /// </summary>
        public int BottomSpace { get { return my_bottom_space; } }

        /// <summary>
        /// Gets a new copy of the document being represented.
        /// </summary>
        /// <param name="doc">The document to create things in</param>
        /// <returns>The node, which still needs to be added to doc</returns>
        public virtual XmlNode GetDocument(XmlDocument doc)
        {
            XmlNode me = doc.CreateElement(Name);
            foreach (Module kid in Children)
            {
                XmlNode xml = kid.GetDocument(doc);
                me.AppendChild(xml);
            }
            foreach (KeyValuePair<String, String> pair
                                in my_attributes)
            {
                XmlAttribute attr = doc.CreateAttribute(pair.Key);
                attr.Value = pair.Value;
                me.Attributes.Append(attr);
            }
            return me;
        }

        /// <summary>
        /// Get the XmlNode that backs this Module.
        /// </summary>
        public XmlNode Node { get { return myNode; } }

        /// <summary>
        /// internally used callback
        /// </summary>
        /// <param name="child"></param>
        /// <param name="at"></param>
        public delegate void ModuleInsertor(Module child, int at);
        /// <summary>
        /// internally used callback
        /// </summary>
        /// <param name="at"></param>
        public delegate void ModuleRemover(int at);
        /// <summary>
        /// Override this to catch child insertions.
        /// For the document to stay synched correctly you must
        /// use base.
        /// 
        /// Dependent on the fact that Inserts can only happen from the
        /// user if the item is selected.
        /// </summary>
        /// <param name="child">the child added</param>
        /// <param name="at">where it's added</param>
        protected virtual void Insert(Module child, int at)
        {
            if (selected)
            { //only want to log it if the user is doing it
                myNode.InsertAfter(child.Node, myNode.ChildNodes[at]);
                AddTag atom = new AddTag(new AbsolutePath(Node),
                                        child.Node,
                                        at);
                my_changes_since_select.Add(atom);
            }
            child.Parent = this;
        }
        /// <summary>
        /// Override this to catch child removals.
        /// For the document to stay synched correctly you must use
        /// make sure the base method is called.
        /// </summary>
        /// <param name="at"></param>
        protected virtual void Remove(int at)
        {
            if (selected)
            {
                XmlNode removed = myNode.ChildNodes[at];
                myNode.RemoveChild(myNode.ChildNodes[at]);
                DeleteTag atom = new DeleteTag(new AbsolutePath(removed), removed);
                my_changes_since_select.Add(atom);
            }
        }

        /// <summary>
        /// Click events are usually handled by the module that gets them, but a module is allowed
        /// to return a different module if it cannot (or will not) handle them.  The default behavior
        /// is to return the module that is clicked.  
        /// 
        /// Click events should only change focus if this method returns a module other than the currently
        /// selected.  Modules may process ClickEvents as interactions, so do not assume
        /// all click events are only focus changes.
        /// <param name="x">The X offset of this click off the top left of this control</param>
        /// <param name="y">The y offset of this click off the top left of this control</param>
        /// </summary>

        public virtual Module ClickEvent(int x, int y)
        {
            return this;
        }

        /// <summary>
        /// Subclasses may provide behaviors on UnSelect.
        /// In the most basic form, they must provide a list of change atoms
        /// that took place during selection that have already not been created.
        /// </summary>
        public virtual List<ChangeAtom> UnSelect()
        {
            selected = false;
            Host.Redraw();
            return my_changes_since_select;
        }

        /// <summary>
        /// Called when a module becomes selected, this virtual method simply
        /// sets selected = true;
        /// </summary>
        public virtual void Select()
        {
            my_changes_since_select = new List<ChangeAtom>();
            selected = true;
            Host.Redraw();
        }

        /// <summary>
        /// Property to inform a element it is hoovered.  Subclasses that change their
        /// size should inform Host by overriding set.
        /// </summary>
        public virtual Boolean Hoover
        {
            get
            {
                return hoovered;
            }
            set
            {
                hoovered = value;
                Host.Redraw();
            }
        }

        /// <summary>
        /// This method calculates the space to draw this module.
        /// It is passed the width available to the module, which is then
        /// used to calculate the height.
        /// 
        /// The implementation in Module simply calculates the sum of the 
        /// heights of all children.  Subclasses should modify this as 
        /// appropriate for their individual drawing needs.
        /// 
        /// Width and Height must be set as a side-effect.
        /// </summary>
        public virtual void Space(int width)
        {
#if DEBUG
            Console.WriteLine("Module.Space: Calculating Space for: " + this.ToString() + " width: " +  width.ToString());
#endif

            int height = 0;
            int calcWidth = 0;
            foreach (Module child in my_kids)
            {
                child.Space(width - my_left_buffer - my_right_buffer);
                height += child.Height + child.TopSpace + child.BottomSpace;
                calcWidth = Math.Max(calcWidth, child.Width);
#if DEBUG
                Console.WriteLine("Module.Space: SPACED CHILD: " + child.ToString());
                Console.WriteLine("Module.Space: Incremented MY height to: " + height);
#endif
            }

            my_width = calcWidth + my_left_buffer + my_right_buffer;
            Height = height + my_bottom_buffer + my_top_buffer;

#if DEBUG
            Console.WriteLine("Module.Space: Final: " + this.ToString());
#endif
            //Make sure empty containers have size to start with.
            if (my_height < my_min_height) Height = my_min_height;
            if (my_width < my_min_width) my_width = my_min_width;
        }

        /// <summary>
        /// Get a DisplayArea that is capable of comositing the module onto the display.
        /// </summary>
        /// <returns>New Display Area that is capable of compositioning this module onto a 
        /// Graphics</returns>
        public abstract DisplayArea GetDisplayArea();
        /*{
            
            my_X = x;
            my_Y = y;
            foreach (Module child in my_kids) {				
                child.Draw(g, x, y, w, child.Height);
                h -= child.Height;
                y += child.Height;
                if (h < 0)
                    throw new Exception("Not enough space to draw!");
            }
        }*/


        /// <summary>
        /// Recursively adds an XmlNode to the Module currentModuleHead.
        /// When this is complete, the entire Xml Tree accessable as
        /// descendants of XmlNode will have an appropriate Module in the Module tree.
        /// 
        /// NOTE:  This method creates child nodes fully populated, it does not modify the type
        /// or content of currentModuleHead except to add children.
        /// 
        /// Do not use this method from the outside.
        /// </summary>
        /// <param name="node">Node to descend through</param>
        /// <param name="currentModuleHead">Current Module to add children to</param>
        public void RecursiveXMLToModule(XmlNode node, Module currentModuleHead)
        {
            currentModuleHead.Children.Clear();
            for (int i = 0; i < node.ChildNodes.Count; ++i)
            {
                if ((node.ChildNodes[i] as XmlElement) != null)
                {
                    Module kid = my_host.Config.GetInstance(
                        node.ChildNodes[i].Name,
                        node.ChildNodes[i],
                        currentModuleHead, my_host);
                    currentModuleHead.Children.Add(kid);
                    RecursiveXMLToModule(node.ChildNodes[i], kid);
                }
                else if ((node.ChildNodes[i] as XmlText) != null)
                {
                    TextModule kid = new TextModule(
                        node.ChildNodes[i],
                        currentModuleHead,
                        my_host,
                        my_host.Config.GetModuleConfig("#PCDATA").TagConfig);
                    currentModuleHead.Children.Add(kid);
                }
            }
        }

        /// <summary>
        /// Create a Module Tree from the specified XmlNode.  All children of document
        /// are added to the tree in the same place.
        /// </summary>
        /// <param name="document"></param>
        public void Setup(XmlNode document)
        {
            RecursiveXMLToModule(document, this);
        }

        /// <summary>
        /// Collection of children (may be used for immutability in the future).
        /// </summary>
        public class ChildCollection : ICollection<Module>, IList<Module>
        {
            List<Module> my_kids;
            ModuleRemover my_remover;
            ModuleInsertor my_insertor;
            /// <summary>
            /// Constructor
            /// </summary>
            public ChildCollection(ModuleInsertor insertor, ModuleRemover remover)
            {
                my_insertor = insertor;
                my_remover = remover;
                my_kids = new List<Module>();
            }


            #region ICollection<Module> Members
            /// <summary>
            /// Add a child to the end
            /// </summary>
            /// <param name="item"></param>
            public void Add(Module item)
            {
                my_insertor(item, my_kids.Count);
                my_kids.Insert(my_kids.Count, item);
            }

            /// <summary>
            /// Clear
            /// </summary>
            public void Clear()
            {
                my_kids.Clear();
            }

            /// <summary>
            /// Contains
            /// </summary>
            /// <param name="item"></param>
            /// <returns></returns>
            public bool Contains(Module item)
            {
                return my_kids.Contains(item);
            }

            /// <summary>
            /// Whatever
            /// </summary>
            /// <param name="array"></param>
            /// <param name="arrayIndex"></param>
            public void CopyTo(Module[] array, int arrayIndex)
            {
                my_kids.CopyTo(array, arrayIndex);
            }

            /// <summary>
            /// Seriously, I'm not documenting this
            /// </summary>
            public int Count
            {
                get
                {
                    return my_kids.Count;
                }
            }

            /// <summary>
            /// I hate this stuff
            /// </summary>
            public bool IsReadOnly
            {
                get
                {
                    return false;
                }
            }

            /// <summary>
            /// See the interfaces
            /// </summary>
            /// <param name="item"></param>
            /// <returns></returns>
            public bool Remove(Module item)
            {
                my_remover(my_kids.IndexOf(item));
                return my_kids.Remove(item);
            }

            #endregion

            #region IEnumerable<Module> Members

            /// <summary>
            /// Yea...
            /// </summary>
            /// <returns></returns>
            public IEnumerator<Module> GetEnumerator()
            {
                return my_kids.GetEnumerator();
            }

            #endregion

            #region IEnumerable Members

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return ((System.Collections.IEnumerable)my_kids).GetEnumerator();
            }

            #endregion

            #region IList<Module> Members

            /// <summary>
            /// NO
            /// </summary>
            /// <param name="item"></param>
            /// <returns></returns>
            public int IndexOf(Module item)
            {
                return my_kids.IndexOf(item);
            }


            /// <summary>
            /// I refuse
            /// </summary>
            /// <param name="index"></param>
            /// <param name="item"></param>
            public void Insert(int index, Module item)
            {
                my_insertor(item, index);
                my_kids.Insert(index, item);
            }

            /// <summary>
            /// To document
            /// </summary>
            /// <param name="index"></param>
            public void RemoveAt(int index)
            {
                my_kids.RemoveAt(index);
            }

            /// <summary>
            /// This nonsense
            /// </summary>
            /// <param name="index"></param>
            /// <returns></returns>
            public Module this[int index]
            {
                get
                {
                    return my_kids[index];
                }
                set
                {
                    my_kids[index] = value;
                }
            }

            #endregion
        }

        /// <summary>
        /// debugging
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return base.ToString() + "{\n"
                + "\tNAME:  " + this.Name + "\n\t" +
                "SELECTED:  " + this.selected + "\n\t" +
                "HOOVER:    " + this.hoovered + "\n\t" +
                "WIDTH:     " + this.Width + "\n\t" +
                "HEIGHT:    " + this.Height + "\n\t" +
                "H_BUFFER:  (" + LeftBuffer + ", " + RightBuffer + ")\n\t" +
                "V_BUFFER:  (" + TopBuffer + ", " + RightBuffer + ")\n\t" +
                "SPACE:     (" + TopSpace + ", " + BottomSpace + ")\n}";
        }

        #region IDisposable Members

        /// <summary>
        /// Allow modules to delete their memory.
        /// </summary>
        public virtual void Dispose()
        {
            //do nothing for the standard case.
        }

        #endregion
    }
}
